Skip to main content

Creating a Cache

Caches already exist as a predefined object in gem5. If you want to look at the source code that makes them tick, you can refer to $GEM_SRC/memory/cache/cache.{hh cc py}, and I encourage you to do so to know all the options available to you in the simulator.

Deriving the Cache

The easiest way to ensure your scripts remain readable (not only to yourself, but also to me 🛸) is to separate your implementation into distinct files for any components you need to modify and the system itself. To that end, this tutorial creates a new file cache.py to store the implementation of the cache system for the processor. So without further ado.

The first thing we need to do is import the cache in the file:

from m5.objects import Cache

Now, we could make all our caches individually as variables from this first cache, however that would produce code that is hard to reuse, so it's better to create a subclass of the cache first to contain implementation details that are common to any derived caches we need.

class L1Cache(Cache):
assoc = 2 # the associativity of the cache
tag_latency = 2 # latency (in cycles) to lookup the tag
data_latency = 2 # latency (in cycles) to access data
response_latency = 2 # latency for the return path on a miss
mshrs = 4 # (Miss Status/Hit Registers)
# The number of MSHRs corresponds to the maximum number of
# outstanding memory access requests currently wanted by
# the processor
# you don't need to know this
tgts_per_mshr = 20 # max number of accesses per MSHR

def connectCPU(self, cpu):
"""
Connect the CPU to the Cache. Must be implemented
in a subclass.
"""
raise NotImplementedError

def connectBus(self, bus):
"""
Connect the cache to the bus. Must be implemented
in a subclass.
"""
self.mem_side = bus.cpu_side_ports

Derived Caches

Now that we have our superclass for our L1 caches, we can derive them easily:

class L1ICache(L1Cache):
size = '16kB'

def connectCPU(self, cpu):
self.cpu_side = cpu.icache_port # why we can't implement this in L1Cache


class L1DCache(L1Cache):
size = '64kB'

def connectCPU(self, cpu):
self.cpu_side = cpu.dcache_port

Note that the CPU already has a defined D-Cache and I-Cache port, so we can just connect our caches to the CPU. This uses a lot of magic behind the scenes in gem5, but we can go over how that works at a later time. Suffice to say that none of that will be necessary to complete your assignments.

L2 Cache

Since our L2 cache has some different requirements, we might as well make another class:

class L2Cache(Cache):
size = '256kB'
assoc = 8
tag_latency = 20
data_latency = 20
response_latency = 20
mshrs = 20
tgts_per_mshr = 12

def connectCPUSideBus(self, bus):
self.cpu_side = bus.mem_side_ports

def connectMemSideBus(self, bus):
self.mem_side = bus.cpu_side_ports

So now we have all of the necessary structures defined, we can move on to modifying the cpu itself.

Code so Far

caches.py
from m5.objects import Cache

# Make a simple L1 cache superclass
class L1Cache(Cache):
assoc = 2
tag_latency = 2
data_latency = 2
response_latency = 2
mshrs = 4
tgts_per_mshr = 20

def connectCPU(self, cpu):
raise NotImplementedError

def connectBus(self, bus):
self.mem_side = bus.cpu_side_ports


# Vary the L1I cache and L1Dcaceh based on that
class L1ICache(L1Cache):
size = '16kB'

def connectCPU(self, cpu):
self.cpu_side = cpu.icache_port


class L1DCache(L1Cache):
size = '64kB'

def connectCPU(self, cpu):
self.cpu_side = cpu.dcache_port


# Some reasonable L2 Cache things
class L2Cache(Cache):
size = '256kB'
assoc = 8
tag_latency = 20
data_latency = 20
response_latency = 20
mshrs = 20
tgts_per_mshr = 12

def connectCPUSideBus(self, bus):
self.cpu_side = bus.mem_side_ports

def connectMemSideBus(self, bus):
self.mem_side = bus.cpu_side_ports